package minimips.isa;

import minimips.computer.Computer;
import minimips.computer.Register;
import minimips.exception.DataHazardException;
import minimips.exception.MemoryInaccessibleException;
import minimips.exception.StopInstructionException;

public abstract class Executable {

	private Register oldId_ex_ir;
	private Register oldMem_wb_ir;
	private Register oldMem_wb_aluOutput;
	
	public void fetch(Computer computer) throws StopInstructionException {
		fetchInstruction(computer);
	}
	
	protected abstract void fetchInstruction(Computer computer) throws StopInstructionException;
	
	public void decode(Computer computer) throws DataHazardException {
		startDecodeInstruction(computer);
		try {
			decodeInstruction(computer);
		} catch (DataHazardException ex) {
			revertDecodeInstruction(computer);
			throw ex;
		}
	}
	
	private void startDecodeInstruction(Computer computer) {
		this.oldId_ex_ir = computer.getId_ex_ir();
		computer.getId_ex_ir().setRegisterValue(computer.getIf_id_ir());
	}
	
	private void revertDecodeInstruction(Computer computer) {
		computer.getId_ex_ir().setRegisterValue(oldId_ex_ir);
	}
	
	protected abstract void decodeInstruction(Computer computer) throws DataHazardException;
	
	public void execute(Computer computer) {
		startExecutePhase(computer);
		executeInstruction(computer);
	}
	
	private void startExecutePhase(Computer computer) {
		computer.getEx_mem_ir().setRegisterValue(computer.getId_ex_ir());
		computer.getEx_mem_b().setRegisterValue(computer.getId_ex_b());
	}
	
	protected abstract void executeInstruction(Computer computer);
	
	public void memoryAccess(Computer computer) throws MemoryInaccessibleException {
		startMemoryAccess(computer);
		try {
			executeMemoryAccess(computer);
		} catch (MemoryInaccessibleException ex) {
			revertMemoryAccess(computer);
			throw ex;
		}
	}
	
	private void startMemoryAccess(Computer computer) {
		this.oldMem_wb_ir = computer.getMem_wb_ir();
		this.oldMem_wb_aluOutput = computer.getMem_wb_aluOutput();
		computer.getMem_wb_ir().setRegisterValue(computer.getEx_mem_ir());
		computer.getMem_wb_aluOutput().setRegisterValue(computer.getEx_mem_aluOutput());
	}
	
	private void revertMemoryAccess(Computer computer) {
		computer.getMem_wb_ir().setRegisterValue(this.oldMem_wb_ir);
		computer.getMem_wb_aluOutput().setRegisterValue(this.oldMem_wb_aluOutput);
	}
	
	protected abstract void executeMemoryAccess(Computer computer) throws MemoryInaccessibleException;
	
	public void writeBack(Computer computer) {
		executeWriteBack(computer);
	}
	
	protected abstract void executeWriteBack(Computer computer);
}
